package com.zjc.netty.client;

import com.zjc.netty.exception.ConnectingException;
import com.zjc.netty.exception.TooBusyException;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.internal.PlatformDependent;

import java.io.Closeable;
import java.net.InetSocketAddress;
import java.util.Deque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class CustomChannelPool implements Closeable {

    private final Deque<Channel> deque;
    private AtomicInteger connectCount = new AtomicInteger(0);
    private AtomicInteger connectingCount = new AtomicInteger(0);
    private Bootstrap bootstrap;
    private int minSize;
    private int maxSize;
    private Long reconnectTime; // 秒 重连3次
    private Long reconnectLongTime; // 分 无限重连

    public CustomChannelPool(Bootstrap bootstrap, int minSize, int maxSize, Long reconnectTime, Long reconnectLongTime) {
        this.deque = PlatformDependent.newConcurrentDeque();
        this.bootstrap = bootstrap;
        this.minSize = minSize;
        this.maxSize = maxSize;
        this.reconnectTime = reconnectTime;
        this.reconnectLongTime = reconnectLongTime;
    }

    public void init() {
        for (int i = (connectCount.get() + connectingCount.get()); i < minSize; i++) {
            connect();
        }
    }

    public void connect() {
        connectingCount.incrementAndGet();

        AtomicInteger reCount = new AtomicInteger();
        bootstrap.connect().addListener((ChannelFuture futureListener) -> {
            final EventLoop eventLoop = futureListener.channel().eventLoop();
            if (!futureListener.isSuccess()) {
                if (connectCount.get() < minSize) {
                    if (reCount.getAndIncrement() < 3) {
                        System.err.printf("客户端已启动，与服务端建立连接失败,%d s之后尝试重连!", reconnectTime);
                        eventLoop.schedule(() -> connect(), reconnectTime, TimeUnit.SECONDS);
                    }
                    if (reCount.getAndIncrement() >= 3) {
                        System.err.printf("客户端已启动，与服务端建立连接失败,%d Minutes之后尝试重连!", reconnectLongTime);
                        eventLoop.schedule(() -> connect(), reconnectLongTime, TimeUnit.MINUTES);
                    }
                }
            }
        });
    }

    public Channel get() throws TooBusyException, ConnectingException {
        if (deque.size() > 0) {
            return deque.pollFirst();
        }
        if ((connectingCount.get() + connectCount.get()) >= minSize
                && deque.size() == 0
                && (connectingCount.get() + connectCount.get()) < maxSize) {
            connect();
            throw new ConnectingException();
        }
        if (connectingCount.get() > 0 && deque.size() == 0) {
            throw new ConnectingException();
        }
        throw new TooBusyException();
    }

    public void release(Channel channel) {
        deque.add(channel);
    }

    public void closeChannel(Channel channel) {
        deque.remove(channel);
        connectCount.decrementAndGet();
        if ((connectCount.get() + connectingCount.get()) < minSize) {
            connect();
        }
    }

    public void addNew(Channel channel) {
        connectCount.incrementAndGet();
        connectingCount.decrementAndGet();

        if ((connectCount.get() + connectingCount.get()) <= maxSize) {
            deque.add(channel);
            System.out.println("addNew");
        } else {
            try {
                channel.close().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();

        CustomChannelPool customChannelPool = new CustomChannelPool(bootstrap, 1, 3, 60l, 30l);

        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .remoteAddress(new InetSocketAddress("127.0.0.1", 7066))
                .handler(new CustomChannelInitializer(customChannelPool));

        customChannelPool.init();

    }

    @Override
    public void close() {
        connectCount.set(0);
        connectingCount.set(0);
    }

    public boolean canClose() {
        return (connectingCount.get() + connectCount.get()) > minSize;
    }
}
